home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / lib / iceweasel / components / FeedWriter.js < prev    next >
Encoding:
Text File  |  2013-01-09  |  46.7 KB  |  1,336 lines

  1. //@line 42 "/tmp/buildd/iceweasel-10.0.12esr/browser/components/feeds/src/FeedWriter.js"
  2.  
  3. const Cc = Components.classes;
  4. const Ci = Components.interfaces;
  5. const Cr = Components.results;
  6. const Cu = Components.utils;
  7.  
  8. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  9.  
  10. const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
  11. const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
  12.  
  13. function LOG(str) {
  14.   var prefB = Cc["@mozilla.org/preferences-service;1"].
  15.               getService(Ci.nsIPrefBranch);
  16.  
  17.   var shouldLog = false;
  18.   try {
  19.     shouldLog = prefB.getBoolPref("feeds.log");
  20.   } 
  21.   catch (ex) {
  22.   }
  23.  
  24.   if (shouldLog)
  25.     dump("*** Feeds: " + str + "\n");
  26. }
  27.  
  28. /**
  29.  * Wrapper function for nsIIOService::newURI.
  30.  * @param aURLSpec
  31.  *        The URL string from which to create an nsIURI.
  32.  * @returns an nsIURI object, or null if the creation of the URI failed.
  33.  */
  34. function makeURI(aURLSpec, aCharset) {
  35.   var ios = Cc["@mozilla.org/network/io-service;1"].
  36.             getService(Ci.nsIIOService);
  37.   try {
  38.     return ios.newURI(aURLSpec, aCharset, null);
  39.   } catch (ex) { }
  40.  
  41.   return null;
  42. }
  43.  
  44. const XML_NS = "http://www.w3.org/XML/1998/namespace"
  45. const HTML_NS = "http://www.w3.org/1999/xhtml";
  46. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  47. const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
  48. const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
  49. const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
  50. const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
  51. const SUBSCRIBE_PAGE_URI = "chrome://browser/content/feeds/subscribe.xhtml";
  52.  
  53. const PREF_SELECTED_APP = "browser.feeds.handlers.application";
  54. const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
  55. const PREF_SELECTED_ACTION = "browser.feeds.handler";
  56. const PREF_SELECTED_READER = "browser.feeds.handler.default";
  57.  
  58. const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
  59. const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
  60. const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
  61. const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
  62.  
  63. const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
  64. const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
  65. const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
  66. const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
  67.  
  68. const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
  69.  
  70. const TITLE_ID = "feedTitleText";
  71. const SUBTITLE_ID = "feedSubtitleText";
  72.  
  73. function getPrefAppForType(t) {
  74.   switch (t) {
  75.     case Ci.nsIFeed.TYPE_VIDEO:
  76.       return PREF_VIDEO_SELECTED_APP;
  77.  
  78.     case Ci.nsIFeed.TYPE_AUDIO:
  79.       return PREF_AUDIO_SELECTED_APP;
  80.  
  81.     default:
  82.       return PREF_SELECTED_APP;
  83.   }
  84. }
  85.  
  86. function getPrefWebForType(t) {
  87.   switch (t) {
  88.     case Ci.nsIFeed.TYPE_VIDEO:
  89.       return PREF_VIDEO_SELECTED_WEB;
  90.  
  91.     case Ci.nsIFeed.TYPE_AUDIO:
  92.       return PREF_AUDIO_SELECTED_WEB;
  93.  
  94.     default:
  95.       return PREF_SELECTED_WEB;
  96.   }
  97. }
  98.  
  99. function getPrefActionForType(t) {
  100.   switch (t) {
  101.     case Ci.nsIFeed.TYPE_VIDEO:
  102.       return PREF_VIDEO_SELECTED_ACTION;
  103.  
  104.     case Ci.nsIFeed.TYPE_AUDIO:
  105.       return PREF_AUDIO_SELECTED_ACTION;
  106.  
  107.     default:
  108.       return PREF_SELECTED_ACTION;
  109.   }
  110. }
  111.  
  112. function getPrefReaderForType(t) {
  113.   switch (t) {
  114.     case Ci.nsIFeed.TYPE_VIDEO:
  115.       return PREF_VIDEO_SELECTED_READER;
  116.  
  117.     case Ci.nsIFeed.TYPE_AUDIO:
  118.       return PREF_AUDIO_SELECTED_READER;
  119.  
  120.     default:
  121.       return PREF_SELECTED_READER;
  122.   }
  123. }
  124.  
  125. /**
  126.  * Converts a number of bytes to the appropriate unit that results in a
  127.  * number that needs fewer than 4 digits
  128.  *
  129.  * @return a pair: [new value with 3 sig. figs., its unit]
  130.   */
  131. function convertByteUnits(aBytes) {
  132.   var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
  133.   let unitIndex = 0;
  134.  
  135.   // convert to next unit if it needs 4 digits (after rounding), but only if
  136.   // we know the name of the next unit
  137.   while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
  138.     aBytes /= 1024;
  139.     unitIndex++;
  140.   }
  141.  
  142.   // Get rid of insignificant bits by truncating to 1 or 0 decimal points
  143.   // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
  144.   aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
  145.  
  146.   return [aBytes, units[unitIndex]];
  147. }
  148.  
  149. function FeedWriter() {}
  150. FeedWriter.prototype = {
  151.   _mimeSvc      : Cc["@mozilla.org/mime;1"].
  152.                   getService(Ci.nsIMIMEService),
  153.  
  154.   _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
  155.     return container.fields.getProperty(property).
  156.                      QueryInterface(Ci.nsIPropertyBag2);
  157.   },
  158.  
  159.   _getPropertyAsString: function FW__getPropertyAsString(container, property) {
  160.     try {
  161.       return container.fields.getPropertyAsAString(property);
  162.     }
  163.     catch (e) {
  164.     }
  165.     return "";
  166.   },
  167.  
  168.   _setContentText: function FW__setContentText(id, text) {
  169.     this._contentSandbox.element = this._document.getElementById(id);
  170.     this._contentSandbox.textNode = this._document.createTextNode(text);
  171.     var codeStr =
  172.       "while (element.hasChildNodes()) " +
  173.       "  element.removeChild(element.firstChild);" +
  174.       "element.appendChild(textNode);";
  175.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  176.     this._contentSandbox.element = null;
  177.     this._contentSandbox.textNode = null;
  178.   },
  179.  
  180.   /**
  181.    * Safely sets the href attribute on an anchor tag, providing the URI 
  182.    * specified can be loaded according to rules. 
  183.    * @param   element
  184.    *          The element to set a URI attribute on
  185.    * @param   attribute
  186.    *          The attribute of the element to set the URI to, e.g. href or src
  187.    * @param   uri
  188.    *          The URI spec to set as the href
  189.    */
  190.   _safeSetURIAttribute: 
  191.   function FW__safeSetURIAttribute(element, attribute, uri) {
  192.     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  193.                  getService(Ci.nsIScriptSecurityManager);    
  194.     const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  195.     try {
  196.       secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
  197.       // checkLoadURIStrWithPrincipal will throw if the link URI should not be
  198.       // loaded, either because our feedURI isn't allowed to load it or per
  199.       // the rules specified in |flags|, so we'll never "linkify" the link...
  200.     }
  201.     catch (e) {
  202.       // Not allowed to load this link because secman.checkLoadURIStr threw
  203.       return;
  204.     }
  205.  
  206.     this._contentSandbox.element = element;
  207.     this._contentSandbox.uri = uri;
  208.     var codeStr = "element.setAttribute('" + attribute + "', uri);";
  209.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  210.   },
  211.  
  212.   /**
  213.    * Use this sandbox to run any dom manipulation code on nodes which
  214.    * are already inserted into the content document.
  215.    */
  216.   __contentSandbox: null,
  217.   get _contentSandbox() {
  218.     if (!this.__contentSandbox)
  219.       this.__contentSandbox = new Cu.Sandbox(this._window, 
  220.                                              {sandboxName: 'FeedWriter'});
  221.  
  222.     return this.__contentSandbox;
  223.   },
  224.  
  225.   /**
  226.    * Calls doCommand for a given XUL element within the context of the
  227.    * content document.
  228.    *
  229.    * @param aElement
  230.    *        the XUL element to call doCommand() on.
  231.    */
  232.   _safeDoCommand: function FW___safeDoCommand(aElement) {
  233.     this._contentSandbox.element = aElement;
  234.     Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
  235.     this._contentSandbox.element = null;
  236.   },
  237.  
  238.   __faviconService: null,
  239.   get _faviconService() {
  240.     if (!this.__faviconService)
  241.       this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
  242.                               getService(Ci.nsIFaviconService);
  243.  
  244.     return this.__faviconService;
  245.   },
  246.  
  247.   __bundle: null,
  248.   get _bundle() {
  249.     if (!this.__bundle) {
  250.       this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
  251.                       getService(Ci.nsIStringBundleService).
  252.                       createBundle(URI_BUNDLE);
  253.     }
  254.     return this.__bundle;
  255.   },
  256.  
  257.   _getFormattedString: function FW__getFormattedString(key, params) {
  258.     return this._bundle.formatStringFromName(key, params, params.length);
  259.   },
  260.   
  261.   _getString: function FW__getString(key) {
  262.     return this._bundle.GetStringFromName(key);
  263.   },
  264.  
  265.   /* Magic helper methods to be used instead of xbl properties */
  266.   _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) {
  267.     var node = aList.firstChild.firstChild;
  268.     while (node) {
  269.       if (node.localName == "menuitem" && node.getAttribute("selected") == "true")
  270.         return node;
  271.  
  272.       node = node.nextSibling;
  273.     }
  274.  
  275.     return null;
  276.   },
  277.  
  278.   _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
  279.     // see checkbox.xml, xbl bindings are not applied within the sandbox!
  280.     this._contentSandbox.checkbox = aCheckbox;
  281.     var codeStr;
  282.     var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
  283.     if (aValue)
  284.       codeStr = "checkbox.setAttribute('checked', 'true'); ";
  285.     else
  286.       codeStr = "checkbox.removeAttribute('checked'); ";
  287.  
  288.     if (change) {
  289.       this._contentSandbox.document = this._document;
  290.       codeStr += "var event = document.createEvent('Events'); " +
  291.                  "event.initEvent('CheckboxStateChange', true, true);" +
  292.                  "checkbox.dispatchEvent(event);"
  293.     }
  294.  
  295.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  296.   },
  297.  
  298.    /**
  299.    * Returns a date suitable for displaying in the feed preview. 
  300.    * If the date cannot be parsed, the return value is "false".
  301.    * @param   dateString
  302.    *          A date as extracted from a feed entry. (entry.updated)
  303.    */
  304.   _parseDate: function FW__parseDate(dateString) {
  305.     // Convert the date into the user's local time zone
  306.     dateObj = new Date(dateString);
  307.  
  308.     // Make sure the date we're given is valid.
  309.     if (!dateObj.getTime())
  310.       return false;
  311.  
  312.     var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
  313.                       getService(Ci.nsIScriptableDateFormat);
  314.     return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
  315.                                       dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
  316.                                       dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
  317.   },
  318.  
  319.   /**
  320.    * Returns the feed type.
  321.    */
  322.   __feedType: null,
  323.   _getFeedType: function FW__getFeedType() {
  324.     if (this.__feedType != null)
  325.       return this.__feedType;
  326.  
  327.     try {
  328.       // grab the feed because it's got the feed.type in it.
  329.       var container = this._getContainer();
  330.       var feed = container.QueryInterface(Ci.nsIFeed);
  331.       this.__feedType = feed.type;
  332.       return feed.type;
  333.     } catch (ex) { }
  334.  
  335.     return Ci.nsIFeed.TYPE_FEED;
  336.   },
  337.  
  338.   /**
  339.    * Maps a feed type to a maybe-feed mimetype.
  340.    */
  341.   _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() {
  342.     switch (this._getFeedType()) {
  343.       case Ci.nsIFeed.TYPE_VIDEO:
  344.         return TYPE_MAYBE_VIDEO_FEED;
  345.  
  346.       case Ci.nsIFeed.TYPE_AUDIO:
  347.         return TYPE_MAYBE_AUDIO_FEED;
  348.  
  349.       default:
  350.         return TYPE_MAYBE_FEED;
  351.     }
  352.   },
  353.  
  354.   /**
  355.    * Writes the feed title into the preview document.
  356.    * @param   container
  357.    *          The feed container
  358.    */
  359.   _setTitleText: function FW__setTitleText(container) {
  360.     if (container.title) {
  361.       var title = container.title.plainText();
  362.       this._setContentText(TITLE_ID, title);
  363.       this._contentSandbox.document = this._document;
  364.       this._contentSandbox.title = title;
  365.       var codeStr = "document.title = title;"
  366.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  367.     }
  368.  
  369.     var feed = container.QueryInterface(Ci.nsIFeed);
  370.     if (feed && feed.subtitle)
  371.       this._setContentText(SUBTITLE_ID, container.subtitle.plainText());
  372.   },
  373.  
  374.   /**
  375.    * Writes the title image into the preview document if one is present.
  376.    * @param   container
  377.    *          The feed container
  378.    */
  379.   _setTitleImage: function FW__setTitleImage(container) {
  380.     try {
  381.       var parts = container.image;
  382.       
  383.       // Set up the title image (supplied by the feed)
  384.       var feedTitleImage = this._document.getElementById("feedTitleImage");
  385.       this._safeSetURIAttribute(feedTitleImage, "src", 
  386.                                 parts.getPropertyAsAString("url"));
  387.  
  388.       // Set up the title image link
  389.       var feedTitleLink = this._document.getElementById("feedTitleLink");
  390.  
  391.       var titleText = this._getFormattedString("linkTitleTextFormat", 
  392.                                                [parts.getPropertyAsAString("title")]);
  393.       this._contentSandbox.feedTitleLink = feedTitleLink;
  394.       this._contentSandbox.titleText = titleText;
  395.       this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
  396.       this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
  397.  
  398.       // Fix the margin on the main title, so that the image doesn't run over
  399.       // the underline
  400.       var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
  401.                     "feedTitleText.style.marginRight = titleImageWidth + 'px';";
  402.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  403.       this._contentSandbox.feedTitleLink = null;
  404.       this._contentSandbox.titleText = null;
  405.       this._contentSandbox.feedTitleText = null;
  406.       this._contentSandbox.titleImageWidth = null;
  407.  
  408.       this._safeSetURIAttribute(feedTitleLink, "href", 
  409.                                 parts.getPropertyAsAString("link"));
  410.     }
  411.     catch (e) {
  412.       LOG("Failed to set Title Image (this is benign): " + e);
  413.     }
  414.   },
  415.  
  416.   /**
  417.    * Writes all entries contained in the feed.
  418.    * @param   container
  419.    *          The container of entries in the feed
  420.    */
  421.   _writeFeedContent: function FW__writeFeedContent(container) {
  422.     // Build the actual feed content
  423.     var feed = container.QueryInterface(Ci.nsIFeed);
  424.     if (feed.items.length == 0)
  425.       return;
  426.  
  427.     this._contentSandbox.feedContent =
  428.       this._document.getElementById("feedContent");
  429.  
  430.     for (var i = 0; i < feed.items.length; ++i) {
  431.       var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  432.       entry.QueryInterface(Ci.nsIFeedContainer);
  433.  
  434.       var entryContainer = this._document.createElementNS(HTML_NS, "div");
  435.       entryContainer.className = "entry";
  436.  
  437.       // If the entry has a title, make it a link
  438.       if (entry.title) {
  439.         var a = this._document.createElementNS(HTML_NS, "a");
  440.         a.appendChild(this._document.createTextNode(entry.title.plainText()));
  441.  
  442.         // Entries are not required to have links, so entry.link can be null.
  443.         if (entry.link)
  444.           this._safeSetURIAttribute(a, "href", entry.link.spec);
  445.  
  446.         var title = this._document.createElementNS(HTML_NS, "h3");
  447.         title.appendChild(a);
  448.  
  449.         var lastUpdated = this._parseDate(entry.updated);
  450.         if (lastUpdated) {
  451.           var dateDiv = this._document.createElementNS(HTML_NS, "div");
  452.           dateDiv.className = "lastUpdated";
  453.           dateDiv.textContent = lastUpdated;
  454.           title.appendChild(dateDiv);
  455.         }
  456.  
  457.         entryContainer.appendChild(title);
  458.       }
  459.  
  460.       var body = this._document.createElementNS(HTML_NS, "div");
  461.       var summary = entry.summary || entry.content;
  462.       var docFragment = null;
  463.       if (summary) {
  464.         if (summary.base)
  465.           body.setAttributeNS(XML_NS, "base", summary.base.spec);
  466.         else
  467.           LOG("no base?");
  468.         docFragment = summary.createDocumentFragment(body);
  469.         if (docFragment)
  470.           body.appendChild(docFragment);
  471.  
  472.         // If the entry doesn't have a title, append a # permalink
  473.         // See http://scripting.com/rss.xml for an example
  474.         if (!entry.title && entry.link) {
  475.           var a = this._document.createElementNS(HTML_NS, "a");
  476.           a.appendChild(this._document.createTextNode("#"));
  477.           this._safeSetURIAttribute(a, "href", entry.link.spec);
  478.           body.appendChild(this._document.createTextNode(" "));
  479.           body.appendChild(a);
  480.         }
  481.  
  482.       }
  483.       body.className = "feedEntryContent";
  484.       entryContainer.appendChild(body);
  485.  
  486.       if (entry.enclosures && entry.enclosures.length > 0) {
  487.         var enclosuresDiv = this._buildEnclosureDiv(entry);
  488.         entryContainer.appendChild(enclosuresDiv);
  489.       }
  490.  
  491.       this._contentSandbox.entryContainer = entryContainer;
  492.       this._contentSandbox.clearDiv =
  493.         this._document.createElementNS(HTML_NS, "div");
  494.       this._contentSandbox.clearDiv.style.clear = "both";
  495.       
  496.       var codeStr = "feedContent.appendChild(entryContainer); " +
  497.                      "feedContent.appendChild(clearDiv);"
  498.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  499.     }
  500.  
  501.     this._contentSandbox.feedContent = null;
  502.     this._contentSandbox.entryContainer = null;
  503.     this._contentSandbox.clearDiv = null;
  504.   },
  505.  
  506.   /**
  507.    * Takes a url to a media item and returns the best name it can come up with.
  508.    * Frequently this is the filename portion (e.g. passing in 
  509.    * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
  510.    * cases, this will return the entire url (e.g. passing in
  511.    * http://example.com/somedirectory/ would return 
  512.    * http://example.com/somedirectory/).
  513.    * @param aURL
  514.    *        The URL string from which to create a display name
  515.    * @returns a string
  516.    */
  517.   _getURLDisplayName: function FW__getURLDisplayName(aURL) {
  518.     var url = makeURI(aURL);
  519.     url.QueryInterface(Ci.nsIURL);
  520.     if (url == null || url.fileName.length == 0)
  521.       return decodeURIComponent(aURL);
  522.  
  523.     return decodeURIComponent(url.fileName);
  524.   },
  525.  
  526.   /**
  527.    * Takes a FeedEntry with enclosures, generates the HTML code to represent
  528.    * them, and returns that.
  529.    * @param   entry
  530.    *          FeedEntry with enclosures
  531.    * @returns element
  532.    */
  533.   _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) {
  534.     var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
  535.     enclosuresDiv.className = "enclosures";
  536.  
  537.     enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
  538.  
  539.     var roundme = function(n) {
  540.       return (Math.round(n * 100) / 100).toLocaleString();
  541.     }
  542.  
  543.     for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
  544.       var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
  545.  
  546.       if (!(enc.hasKey("url"))) 
  547.         continue;
  548.  
  549.       var enclosureDiv = this._document.createElementNS(HTML_NS, "div");
  550.       enclosureDiv.setAttribute("class", "enclosure");
  551.  
  552.       var mozicon = "moz-icon://.txt?size=16";
  553.       var type_text = null;
  554.       var size_text = null;
  555.  
  556.       if (enc.hasKey("type")) {
  557.         type_text = enc.get("type");
  558.         try {
  559.           var handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
  560.  
  561.           if (handlerInfoWrapper)
  562.             type_text = handlerInfoWrapper.description;
  563.  
  564.           if  (type_text && type_text.length > 0)
  565.             mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
  566.  
  567.         } catch (ex) { }
  568.  
  569.       }
  570.  
  571.       if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
  572.         var enc_size = convertByteUnits(parseInt(enc.get("length")));
  573.  
  574.         var size_text = this._getFormattedString("enclosureSizeText", 
  575.                              [enc_size[0], this._getString(enc_size[1])]);
  576.       }
  577.  
  578.       var iconimg = this._document.createElementNS(HTML_NS, "img");
  579.       iconimg.setAttribute("src", mozicon);
  580.       iconimg.setAttribute("class", "type-icon");
  581.       enclosureDiv.appendChild(iconimg);
  582.  
  583.       enclosureDiv.appendChild(this._document.createTextNode( " " ));
  584.  
  585.       var enc_href = this._document.createElementNS(HTML_NS, "a");
  586.       enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
  587.       this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
  588.       enclosureDiv.appendChild(enc_href);
  589.  
  590.       if (type_text && size_text)
  591.         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
  592.  
  593.       else if (type_text) 
  594.         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
  595.  
  596.       else if (size_text)
  597.         enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
  598.  
  599.       enclosuresDiv.appendChild(enclosureDiv);
  600.     }
  601.  
  602.     return enclosuresDiv;
  603.   },
  604.  
  605.   /**
  606.    * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
  607.    * Displays error information if there was one.
  608.    * @param   result
  609.    *          The parsed feed result
  610.    * @returns A valid nsIFeedContainer object containing the contents of
  611.    *          the feed.
  612.    */
  613.   _getContainer: function FW__getContainer(result) {
  614.     var feedService = 
  615.         Cc["@mozilla.org/browser/feeds/result-service;1"].
  616.         getService(Ci.nsIFeedResultService);
  617.  
  618.     try {
  619.       var result = 
  620.         feedService.getFeedResult(this._getOriginalURI(this._window));
  621.     }
  622.     catch (e) {
  623.       LOG("Subscribe Preview: feed not available?!");
  624.     }
  625.     
  626.     if (result.bozo) {
  627.       LOG("Subscribe Preview: feed result is bozo?!");
  628.     }
  629.  
  630.     try {
  631.       var container = result.doc;
  632.     }
  633.     catch (e) {
  634.       LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
  635.       return null;
  636.     }
  637.     return container;
  638.   },
  639.  
  640.   /**
  641.    * Get the human-readable display name of a file. This could be the 
  642.    * application name.
  643.    * @param   file
  644.    *          A nsIFile to look up the name of
  645.    * @returns The display name of the application represented by the file.
  646.    */
  647.   _getFileDisplayName: function FW__getFileDisplayName(file) {
  648. //@line 702 "/tmp/buildd/iceweasel-10.0.12esr/browser/components/feeds/src/FeedWriter.js"
  649.     return file.leafName;
  650.   },
  651.  
  652.   /**
  653.    * Get moz-icon url for a file
  654.    * @param   file
  655.    *          A nsIFile object for which the moz-icon:// is returned
  656.    * @returns moz-icon url of the given file as a string
  657.    */
  658.   _getFileIconURL: function FW__getFileIconURL(file) {
  659.     var ios = Cc["@mozilla.org/network/io-service;1"].
  660.               getService(Components.interfaces.nsIIOService);
  661.     var fph = ios.getProtocolHandler("file")
  662.                  .QueryInterface(Ci.nsIFileProtocolHandler);
  663.     var urlSpec = fph.getURLSpecFromFile(file);
  664.     return "moz-icon://" + urlSpec + "?size=16";
  665.   },
  666.  
  667.   /**
  668.    * Helper method to set the selected application and system default
  669.    * reader menuitems details from a file object
  670.    *   @param aMenuItem
  671.    *          The menuitem on which the attributes should be set
  672.    *   @param aFile
  673.    *          The menuitem's associated file
  674.    */
  675.   _initMenuItemWithFile: function(aMenuItem, aFile) {
  676.     this._contentSandbox.menuitem = aMenuItem;
  677.     this._contentSandbox.label = this._getFileDisplayName(aFile);
  678.     this._contentSandbox.image = this._getFileIconURL(aFile);
  679.     var codeStr = "menuitem.setAttribute('label', label); " +
  680.                   "menuitem.setAttribute('image', image);"
  681.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  682.   },
  683.  
  684.   /**
  685.    * Helper method to get an element in the XBL binding where the handler
  686.    * selection UI lives
  687.    */
  688.   _getUIElement: function FW__getUIElement(id) {
  689.     return this._document.getAnonymousElementByAttribute(
  690.       this._document.getElementById("feedSubscribeLine"), "anonid", id);
  691.   },
  692.  
  693.   /**
  694.    * Displays a prompt from which the user may choose a (client) feed reader.
  695.    * @return - true if a feed reader was selected, false otherwise.
  696.    */
  697.   _chooseClientApp: function FW__chooseClientApp() {
  698.     try {
  699.       var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  700.       fp.init(this._window,
  701.               this._getString("chooseApplicationDialogTitle"),
  702.               Ci.nsIFilePicker.modeOpen);
  703.       fp.appendFilters(Ci.nsIFilePicker.filterApps);
  704.  
  705.       if (fp.show() == Ci.nsIFilePicker.returnOK) {
  706.         this._selectedApp = fp.file;
  707.         if (this._selectedApp) {
  708.           // XXXben - we need to compare this with the running instance executable
  709.           //          just don't know how to do that via script...
  710.           // XXXmano TBD: can probably add this to nsIShellService
  711. //@line 770 "/tmp/buildd/iceweasel-10.0.12esr/browser/components/feeds/src/FeedWriter.js"
  712.           if (fp.file.leafName != "iceweasel-bin") {
  713. //@line 773 "/tmp/buildd/iceweasel-10.0.12esr/browser/components/feeds/src/FeedWriter.js"
  714.             this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
  715.                                        this._selectedApp);
  716.  
  717.             // Show and select the selected application menuitem
  718.             var codeStr = "selectedAppMenuItem.hidden = false;" +
  719.                           "selectedAppMenuItem.doCommand();"
  720.             Cu.evalInSandbox(codeStr, this._contentSandbox);
  721.             return true;
  722.           }
  723.         }
  724.       }
  725.     }
  726.     catch(ex) { }
  727.  
  728.     return false;
  729.   },
  730.  
  731.   _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
  732.     var checkbox = this._getUIElement("alwaysUse");
  733.     if (checkbox) {
  734.       var alwaysUse = false;
  735.       try {
  736.         var prefs = Cc["@mozilla.org/preferences-service;1"].
  737.                     getService(Ci.nsIPrefBranch);
  738.         if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
  739.           alwaysUse = true;
  740.       }
  741.       catch(ex) { }
  742.       this._setCheckboxCheckedState(checkbox, alwaysUse);
  743.     }
  744.   },
  745.  
  746.   _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
  747.     var stringLabel = "subscribeFeedUsing";
  748.     switch (this._getFeedType()) {
  749.       case Ci.nsIFeed.TYPE_VIDEO:
  750.         stringLabel = "subscribeVideoPodcastUsing";
  751.         break;
  752.  
  753.       case Ci.nsIFeed.TYPE_AUDIO:
  754.         stringLabel = "subscribeAudioPodcastUsing";
  755.         break;
  756.     }
  757.  
  758.     this._contentSandbox.subscribeUsing =
  759.       this._getUIElement("subscribeUsingDescription");
  760.     this._contentSandbox.label = this._getString(stringLabel);
  761.     var codeStr = "subscribeUsing.setAttribute('value', label);"
  762.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  763.   },
  764.  
  765.   _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
  766.     var checkbox = this._getUIElement("alwaysUse");
  767.     if (checkbox) {
  768.       if (this._handlersMenuList) {
  769.         var handlerName = this._getSelectedItemFromMenulist(this._handlersMenuList)
  770.                               .getAttribute("label");
  771.         var stringLabel = "alwaysUseForFeeds";
  772.         switch (this._getFeedType()) {
  773.           case Ci.nsIFeed.TYPE_VIDEO:
  774.             stringLabel = "alwaysUseForVideoPodcasts";
  775.             break;
  776.  
  777.           case Ci.nsIFeed.TYPE_AUDIO:
  778.             stringLabel = "alwaysUseForAudioPodcasts";
  779.             break;
  780.         }
  781.  
  782.         this._contentSandbox.checkbox = checkbox;
  783.         this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
  784.         
  785.         var codeStr = "checkbox.setAttribute('label', label);";
  786.         Cu.evalInSandbox(codeStr, this._contentSandbox);
  787.       }
  788.     }
  789.   },
  790.  
  791.   // nsIDomEventListener
  792.   handleEvent: function(event) {
  793.     if (event.target.ownerDocument != this._document) {
  794.       LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
  795.       return;
  796.     }
  797.  
  798.     if (event.type == "command") {
  799.       switch (event.target.getAttribute("anonid")) {
  800.         case "subscribeButton":
  801.           this.subscribe();
  802.           break;
  803.         case "chooseApplicationMenuItem":
  804.           /* Bug 351263: Make sure to not steal focus if the "Choose
  805.            * Application" item is being selected with the keyboard. We do this
  806.            * by ignoring command events while the dropdown is closed (user
  807.            * arrowing through the combobox), but handling them while the
  808.            * combobox dropdown is open (user pressed enter when an item was
  809.            * selected). If we don't show the filepicker here, it will be shown
  810.            * when clicking "Subscribe Now".
  811.            */
  812.           var popupbox = this._handlersMenuList.firstChild.boxObject;
  813.           popupbox.QueryInterface(Components.interfaces.nsIPopupBoxObject);
  814.           if (popupbox.popupState == "hiding" && !this._chooseClientApp()) {
  815.             // Select the (per-prefs) selected handler if no application was
  816.             // selected
  817.             this._setSelectedHandler(this._getFeedType());
  818.           }
  819.           break;
  820.         default:
  821.           this._setAlwaysUseLabel();
  822.       }
  823.     }
  824.   },
  825.  
  826.   _setSelectedHandler: function FW__setSelectedHandler(feedType) {
  827.     var prefs =   
  828.         Cc["@mozilla.org/preferences-service;1"].
  829.         getService(Ci.nsIPrefBranch);
  830.  
  831.     var handler = "bookmarks";
  832.     try {
  833.       handler = prefs.getCharPref(getPrefReaderForType(feedType));
  834.     }
  835.     catch (ex) { }
  836.  
  837.     switch (handler) {
  838.       case "web": {
  839.         if (this._handlersMenuList) {
  840.           var url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
  841.           var handlers =
  842.             this._handlersMenuList.getElementsByAttribute("webhandlerurl", url);
  843.           if (handlers.length == 0) {
  844.             LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
  845.             return;
  846.           }
  847.  
  848.           this._safeDoCommand(handlers[0]);
  849.         }
  850.         break;
  851.       }
  852.       case "client": {
  853.         try {
  854.           this._selectedApp =
  855.             prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
  856.         }
  857.         catch(ex) {
  858.           this._selectedApp = null;
  859.         }
  860.  
  861.         if (this._selectedApp) {
  862.           this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
  863.                                      this._selectedApp);
  864.           var codeStr = "selectedAppMenuItem.hidden = false; " +
  865.                         "selectedAppMenuItem.doCommand(); ";
  866.  
  867.           // Only show the default reader menuitem if the default reader
  868.           // isn't the selected application
  869.           if (this._defaultSystemReader) {
  870.             var shouldHide =
  871.               this._defaultSystemReader.path == this._selectedApp.path;
  872.             codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
  873.           }
  874.           Cu.evalInSandbox(codeStr, this._contentSandbox);
  875.           break;
  876.         }
  877.       }
  878.       case "bookmarks":
  879.       default: {
  880.         var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
  881.         if (liveBookmarksMenuItem)
  882.           this._safeDoCommand(liveBookmarksMenuItem);
  883.       } 
  884.     }
  885.   },
  886.  
  887.   _initSubscriptionUI: function FW__initSubscriptionUI() {
  888.     var handlersMenuPopup = this._getUIElement("handlersMenuPopup");
  889.     if (!handlersMenuPopup)
  890.       return;
  891.  
  892.     var feedType = this._getFeedType();
  893.     var codeStr;
  894.  
  895.     // change the background
  896.     var header = this._document.getElementById("feedHeader");
  897.     this._contentSandbox.header = header;
  898.     switch (feedType) {
  899.       case Ci.nsIFeed.TYPE_VIDEO:
  900.         codeStr = "header.className = 'videoPodcastBackground'; ";
  901.         break;
  902.  
  903.       case Ci.nsIFeed.TYPE_AUDIO:
  904.         codeStr = "header.className = 'audioPodcastBackground'; ";
  905.         break;
  906.  
  907.       default:
  908.         codeStr = "header.className = 'feedBackground'; ";
  909.     }
  910.  
  911.     var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
  912.  
  913.     // Last-selected application
  914.     var menuItem = liveBookmarksMenuItem.cloneNode(false);
  915.     menuItem.removeAttribute("selected");
  916.     menuItem.setAttribute("anonid", "selectedAppMenuItem");
  917.     menuItem.className = "menuitem-iconic selectedAppMenuItem";
  918.     menuItem.setAttribute("handlerType", "client");
  919.     try {
  920.       var prefs = Cc["@mozilla.org/preferences-service;1"].
  921.                   getService(Ci.nsIPrefBranch);
  922.       this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
  923.                                                 Ci.nsILocalFile);
  924.  
  925.       if (this._selectedApp.exists())
  926.         this._initMenuItemWithFile(menuItem, this._selectedApp);
  927.       else {
  928.         // Hide the menuitem if the last selected application doesn't exist
  929.         menuItem.setAttribute("hidden", true);
  930.       }
  931.     }
  932.     catch(ex) {
  933.       // Hide the menuitem until an application is selected
  934.       menuItem.setAttribute("hidden", true);
  935.     }
  936.     this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
  937.     this._contentSandbox.selectedAppMenuItem = menuItem;
  938.     
  939.     codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
  940.  
  941.     // List the default feed reader
  942.     try {
  943.       this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
  944.                                   getService(Ci.nsIShellService).
  945.                                   defaultFeedReader;
  946.       menuItem = liveBookmarksMenuItem.cloneNode(false);
  947.       menuItem.removeAttribute("selected");
  948.       menuItem.setAttribute("anonid", "defaultHandlerMenuItem");
  949.       menuItem.className = "menuitem-iconic defaultHandlerMenuItem";
  950.       menuItem.setAttribute("handlerType", "client");
  951.  
  952.       this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
  953.  
  954.       // Hide the default reader item if it points to the same application
  955.       // as the last-selected application
  956.       if (this._selectedApp &&
  957.           this._selectedApp.path == this._defaultSystemReader.path)
  958.         menuItem.hidden = true;
  959.     }
  960.     catch(ex) { menuItem = null; /* no default reader */ }
  961.  
  962.     if (menuItem) {
  963.       this._contentSandbox.defaultHandlerMenuItem = menuItem;
  964.       codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
  965.     }
  966.  
  967.     // "Choose Application..." menuitem
  968.     menuItem = liveBookmarksMenuItem.cloneNode(false);
  969.     menuItem.removeAttribute("selected");
  970.     menuItem.setAttribute("anonid", "chooseApplicationMenuItem");
  971.     menuItem.className = "menuitem-iconic chooseApplicationMenuItem";
  972.     menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
  973.  
  974.     this._contentSandbox.chooseAppMenuItem = menuItem;
  975.     codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
  976.  
  977.     // separator
  978.     this._contentSandbox.chooseAppSep =
  979.       menuItem = liveBookmarksMenuItem.nextSibling.cloneNode(false);
  980.     codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
  981.  
  982.     Cu.evalInSandbox(codeStr, this._contentSandbox);
  983.  
  984.     // List of web handlers
  985.     var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  986.                getService(Ci.nsIWebContentConverterService);
  987.     var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
  988.     if (handlers.length != 0) {
  989.       for (var i = 0; i < handlers.length; ++i) {
  990.         menuItem = liveBookmarksMenuItem.cloneNode(false);
  991.         menuItem.removeAttribute("selected");
  992.         menuItem.className = "menuitem-iconic";
  993.         menuItem.setAttribute("label", handlers[i].name);
  994.         menuItem.setAttribute("handlerType", "web");
  995.         menuItem.setAttribute("webhandlerurl", handlers[i].uri);
  996.         this._contentSandbox.menuItem = menuItem;
  997.         codeStr = "handlersMenuPopup.appendChild(menuItem);";
  998.         Cu.evalInSandbox(codeStr, this._contentSandbox);
  999.  
  1000.         this._setFaviconForWebReader(handlers[i].uri, menuItem);
  1001.       }
  1002.       this._contentSandbox.menuItem = null;
  1003.     }
  1004.  
  1005.     this._setSelectedHandler(feedType);
  1006.  
  1007.     // "Subscribe using..."
  1008.     this._setSubscribeUsingLabel();
  1009.  
  1010.     // "Always use..." checkbox initial state
  1011.     this._setAlwaysUseCheckedState(feedType);
  1012.     this._setAlwaysUseLabel();
  1013.  
  1014.     // We update the "Always use.." checkbox label whenever the selected item
  1015.     // in the list is changed
  1016.     handlersMenuPopup.addEventListener("command", this, false);
  1017.  
  1018.     // Set up the "Subscribe Now" button
  1019.     this._getUIElement("subscribeButton")
  1020.         .addEventListener("command", this, false);
  1021.  
  1022.     // first-run ui
  1023.     var showFirstRunUI = true;
  1024.     try {
  1025.       showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
  1026.     }
  1027.     catch (ex) { }
  1028.     if (showFirstRunUI) {
  1029.       var textfeedinfo1, textfeedinfo2;
  1030.       switch (feedType) {
  1031.         case Ci.nsIFeed.TYPE_VIDEO:
  1032.           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
  1033.           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
  1034.           break;
  1035.         case Ci.nsIFeed.TYPE_AUDIO:
  1036.           textfeedinfo1 = "feedSubscriptionAudioPodcast1";
  1037.           textfeedinfo2 = "feedSubscriptionAudioPodcast2";
  1038.           break;
  1039.         default:
  1040.           textfeedinfo1 = "feedSubscriptionFeed1";
  1041.           textfeedinfo2 = "feedSubscriptionFeed2";
  1042.       }
  1043.  
  1044.       this._contentSandbox.feedinfo1 =
  1045.         this._document.getElementById("feedSubscriptionInfo1");
  1046.       this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
  1047.       this._contentSandbox.feedinfo2 =
  1048.         this._document.getElementById("feedSubscriptionInfo2");
  1049.       this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
  1050.       this._contentSandbox.header = header;
  1051.       codeStr = "feedinfo1.textContent = feedinfo1Str; " +
  1052.                 "feedinfo2.textContent = feedinfo2Str; " +
  1053.                 "header.setAttribute('firstrun', 'true');"
  1054.       Cu.evalInSandbox(codeStr, this._contentSandbox);
  1055.       prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
  1056.     }
  1057.   },
  1058.  
  1059.   /**
  1060.    * Returns the original URI object of the feed and ensures that this
  1061.    * component is only ever invoked from the preview document.  
  1062.    * @param aWindow 
  1063.    *        The window of the document invoking the BrowserFeedWriter
  1064.    */
  1065.   _getOriginalURI: function FW__getOriginalURI(aWindow) {
  1066.     var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  1067.                getInterface(Ci.nsIWebNavigation).
  1068.                QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
  1069.  
  1070.     var uri = makeURI(SUBSCRIBE_PAGE_URI);
  1071.     var resolvedURI = Cc["@mozilla.org/chrome/chrome-registry;1"].
  1072.                       getService(Ci.nsIChromeRegistry).
  1073.                       convertChromeURL(uri);
  1074.  
  1075.     if (resolvedURI.equals(chan.URI))
  1076.       return chan.originalURI;
  1077.  
  1078.     return null;
  1079.   },
  1080.  
  1081.   _window: null,
  1082.   _document: null,
  1083.   _feedURI: null,
  1084.   _feedPrincipal: null,
  1085.   _handlersMenuList: null,
  1086.  
  1087.   // nsIFeedWriter
  1088.   init: function FW_init(aWindow) {
  1089.     var window = aWindow;
  1090.     this._feedURI = this._getOriginalURI(window);
  1091.     if (!this._feedURI)
  1092.       return;
  1093.  
  1094.     this._window = window;
  1095.     this._document = window.document;
  1096.     this._document.getElementById("feedSubscribeLine").offsetTop;
  1097.     this._handlersMenuList = this._getUIElement("handlersMenuList");
  1098.  
  1099.     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1100.                  getService(Ci.nsIScriptSecurityManager);
  1101.     this._feedPrincipal = secman.getCodebasePrincipal(this._feedURI);
  1102.  
  1103.     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
  1104.  
  1105.     // Set up the subscription UI
  1106.     this._initSubscriptionUI();
  1107.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1108.                 getService(Ci.nsIPrefBranch2);
  1109.     prefs.addObserver(PREF_SELECTED_ACTION, this, false);
  1110.     prefs.addObserver(PREF_SELECTED_READER, this, false);
  1111.     prefs.addObserver(PREF_SELECTED_WEB, this, false);
  1112.     prefs.addObserver(PREF_SELECTED_APP, this, false);
  1113.     prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
  1114.     prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
  1115.     prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
  1116.     prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
  1117.  
  1118.     prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
  1119.     prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
  1120.     prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
  1121.     prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
  1122.   },
  1123.  
  1124.   writeContent: function FW_writeContent() {
  1125.     if (!this._window)
  1126.       return;
  1127.  
  1128.     try {
  1129.       // Set up the feed content
  1130.       var container = this._getContainer();
  1131.       if (!container)
  1132.         return;
  1133.  
  1134.       this._setTitleText(container);
  1135.       this._setTitleImage(container);
  1136.       this._writeFeedContent(container);
  1137.     }
  1138.     finally {
  1139.       this._removeFeedFromCache();
  1140.     }
  1141.   },
  1142.  
  1143.   close: function FW_close() {
  1144.     this._getUIElement("handlersMenuPopup")
  1145.         .removeEventListener("command", this, false);
  1146.     this._getUIElement("subscribeButton")
  1147.         .removeEventListener("command", this, false);
  1148.     this._document = null;
  1149.     this._window = null;
  1150.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1151.                 getService(Ci.nsIPrefBranch2);
  1152.     prefs.removeObserver(PREF_SELECTED_ACTION, this);
  1153.     prefs.removeObserver(PREF_SELECTED_READER, this);
  1154.     prefs.removeObserver(PREF_SELECTED_WEB, this);
  1155.     prefs.removeObserver(PREF_SELECTED_APP, this);
  1156.     prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
  1157.     prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
  1158.     prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
  1159.     prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
  1160.  
  1161.     prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
  1162.     prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
  1163.     prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
  1164.     prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
  1165.  
  1166.     this._removeFeedFromCache();
  1167.     this.__faviconService = null;
  1168.     this.__bundle = null;
  1169.     this._feedURI = null;
  1170.     this.__contentSandbox = null;
  1171.   },
  1172.  
  1173.   _removeFeedFromCache: function FW__removeFeedFromCache() {
  1174.     if (this._feedURI) {
  1175.       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1176.                         getService(Ci.nsIFeedResultService);
  1177.       feedService.removeFeedResult(this._feedURI);
  1178.       this._feedURI = null;
  1179.     }
  1180.   },
  1181.  
  1182.   subscribe: function FW_subscribe() {
  1183.     var feedType = this._getFeedType();
  1184.  
  1185.     // Subscribe to the feed using the selected handler and save prefs
  1186.     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1187.                 getService(Ci.nsIPrefBranch);
  1188.     var defaultHandler = "reader";
  1189.     var useAsDefault = this._getUIElement("alwaysUse").getAttribute("checked");
  1190.  
  1191.     var selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
  1192.  
  1193.     // Show the file picker before subscribing if the
  1194.     // choose application menuitem was chosen using the keyboard
  1195.     if (selectedItem.getAttribute("anonid") == "chooseApplicationMenuItem") {
  1196.       if (!this._chooseClientApp())
  1197.         return;
  1198.       
  1199.       selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
  1200.     }
  1201.  
  1202.     if (selectedItem.hasAttribute("webhandlerurl")) {
  1203.       var webURI = selectedItem.getAttribute("webhandlerurl");
  1204.       prefs.setCharPref(getPrefReaderForType(feedType), "web");
  1205.  
  1206.       var supportsString = Cc["@mozilla.org/supports-string;1"].
  1207.                            createInstance(Ci.nsISupportsString);
  1208.       supportsString.data = webURI;
  1209.       prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString,
  1210.                             supportsString);
  1211.  
  1212.       var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1213.                  getService(Ci.nsIWebContentConverterService);
  1214.       var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
  1215.       if (handler) {
  1216.         if (useAsDefault)
  1217.           wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
  1218.  
  1219.         this._window.location.href = handler.getHandlerURI(this._window.location.href);
  1220.       }
  1221.     }
  1222.     else {
  1223.       switch (selectedItem.getAttribute("anonid")) {
  1224.         case "selectedAppMenuItem":
  1225.           prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1226.                                 this._selectedApp);
  1227.           prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1228.           break;
  1229.         case "defaultHandlerMenuItem":
  1230.           prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1231.                                 this._defaultSystemReader);
  1232.           prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1233.           break;
  1234.         case "liveBookmarksMenuItem":
  1235.           defaultHandler = "bookmarks";
  1236.           prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks");
  1237.           break;
  1238.       }
  1239.       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1240.                         getService(Ci.nsIFeedResultService);
  1241.  
  1242.       // Pull the title and subtitle out of the document
  1243.       var feedTitle = this._document.getElementById(TITLE_ID).textContent;
  1244.       var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
  1245.       feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
  1246.     }
  1247.  
  1248.     // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
  1249.     // to either "reader" (If a web reader or if an application is selected),
  1250.     // or to "bookmarks" (if the live bookmarks option is selected).
  1251.     // Otherwise, we should set it to "ask"
  1252.     if (useAsDefault)
  1253.       prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
  1254.     else
  1255.       prefs.setCharPref(getPrefActionForType(feedType), "ask");
  1256.   },
  1257.  
  1258.   // nsIObserver
  1259.   observe: function FW_observe(subject, topic, data) {
  1260.     if (!this._window) {
  1261.       // this._window is null unless this.init was called with a trusted
  1262.       // window object.
  1263.       return;
  1264.     }
  1265.  
  1266.     var feedType = this._getFeedType();
  1267.  
  1268.     if (topic == "nsPref:changed") {
  1269.       switch (data) {
  1270.         case PREF_SELECTED_READER:
  1271.         case PREF_SELECTED_WEB:
  1272.         case PREF_SELECTED_APP:
  1273.         case PREF_VIDEO_SELECTED_READER:
  1274.         case PREF_VIDEO_SELECTED_WEB:
  1275.         case PREF_VIDEO_SELECTED_APP:
  1276.         case PREF_AUDIO_SELECTED_READER:
  1277.         case PREF_AUDIO_SELECTED_WEB:
  1278.         case PREF_AUDIO_SELECTED_APP:
  1279.           this._setSelectedHandler(feedType);
  1280.           break;
  1281.         case PREF_SELECTED_ACTION:
  1282.         case PREF_VIDEO_SELECTED_ACTION:
  1283.         case PREF_AUDIO_SELECTED_ACTION:
  1284.           this._setAlwaysUseCheckedState(feedType);
  1285.       }
  1286.     } 
  1287.   },
  1288.  
  1289.   /**
  1290.    * Sets the icon for the given web-reader item in the readers menu.
  1291.    * The icon is fetched and stored through the favicon service.
  1292.    *
  1293.    * @param aReaderUrl
  1294.    *        the reader url.
  1295.    * @param aMenuItem
  1296.    *        the reader item in the readers menulist.
  1297.    *
  1298.    * @note For privacy reasons we cannot set the image attribute directly
  1299.    *       to the icon url.  See Bug 358878 for details.
  1300.    */
  1301.   _setFaviconForWebReader:
  1302.   function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) {
  1303.     var readerURI = makeURI(aReaderUrl);
  1304.     if (!/^https?/.test(readerURI.scheme)) {
  1305.       // Don't try to get a favicon for non http(s) URIs.
  1306.       return;
  1307.     }
  1308.     var faviconURI = makeURI(readerURI.prePath + "/favicon.ico");
  1309.     var self = this;
  1310.     this._faviconService.setAndLoadFaviconForPage(readerURI, faviconURI, false,
  1311.       function (aURI, aDataLen, aData, aMimeType) {
  1312.         if (aDataLen > 0) {
  1313.           var dataURL = "data:" + aMimeType + ";base64," +
  1314.                         btoa(String.fromCharCode.apply(null, aData));
  1315.           self._contentSandbox.menuItem = aMenuItem;
  1316.           self._contentSandbox.dataURL = dataURL;
  1317.           var codeStr = "menuItem.setAttribute('image', dataURL);";
  1318.           Cu.evalInSandbox(codeStr, self._contentSandbox);
  1319.           self._contentSandbox.menuItem = null;
  1320.           self._contentSandbox.dataURL = null;
  1321.         }
  1322.       });
  1323.   },
  1324.  
  1325.   classID: FEEDWRITER_CID,
  1326.   classInfo: XPCOMUtils.generateCI({classID: FEEDWRITER_CID,
  1327.                                     contractID: FEEDWRITER_CONTRACTID,
  1328.                                     interfaces: [Ci.nsIFeedWriter],
  1329.                                     flags: Ci.nsIClassInfo.DOM_OBJECT}),
  1330.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedWriter,
  1331.                                          Ci.nsIDOMEventListener, Ci.nsIObserver,
  1332.                                          Ci.nsINavHistoryObserver])
  1333. };
  1334.  
  1335. var NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);
  1336.